#!/usr/bin/python
import struct

class Element(object):
	@classmethod
	def __getformat__(cls):
		def getfmt(x):
			if type(x[1]) == str:
				return x[1]
			else:
				subfmt, subsize = x[1].__getformat__()
				return "%ds" % subsize

		format = "<" + "".join(map(getfmt, cls.__ATTRS__))
		size = struct.calcsize(format)
		return format, size
	def __init__(self, f, s = None):
		format, size = self.__getformat__()
		if type(s) == str:
			data = s
		else:
			data = f.read(size)
		if len(data) < size:
			print "ERROR", len(data), "<", size
			return
		values = struct.unpack(format, data)
		for index, info in enumerate(self.__ATTRS__):
			name, t = info
			value = values[index]
			if type(t) != str:
				value = t(f, value)
			setattr(self, name, value)
	def __repr__(self):
		if hasattr(self, '__ATTR_REPR__'):
			repr = self.__ATTR_REPR__
		else:
			repr = map(lambda x: x[0], self.__ATTRS__)
		def tostr(x):
			value = getattr(self, x)
			if type(value) == float:
				return "%.1f" % value
			else:
				return str(value)
		return "%s<%s>" % (self.__class__.__name__,
			", ".join(map(tostr, repr)))


class VertexList(Element):
	__ATTRS__ = (
		("count", "i"),
		("offset", "i"),
	)
	__ATTR_REPR__ = ("count", "elements")
	def __init__(self, f, s = None):
		Element.__init__(self, f, s)
		assert (self.count % 3) == 0
		self.count /= 3
		pos = f.tell()
		f.seek(self.offset, 0)
		def const(f):
			return struct.unpack("<3H", f.read(6))
		self.elements = [const(f) for i in range(self.count)]
		f.seek(pos, 0)

def MakeValue(s, name = None):
	class C(Element):
		__ATTRS__ = ( ( "value", s), )
	if name:
		C.__name__ = name
	else:
		C.__name__ = "Value_%s" % s
	return C

def MakeList(cls):
	if type(cls) == str:
		cls = MakeValue(cls)
	class ListElement(Element):
		CLASS = cls
		__ATTRS__ = (
			("count", "i"),
			("offset", "i"),
		)
		__ATTR_REPR__ = ("count", "elements")
		def __init__(self, f, s = None):
			Element.__init__(self, f, s)
			pos = f.tell()
			f.seek(self.offset, 0)
			self.elements = [self.CLASS(f) for i in range(self.count)]
			f.seek(pos, 0)
	ListElement.__name__ = cls.__name__ + "List"
	return ListElement

class StringElement(Element):
	__ATTR_REPR__ = ("value",)
	__ATTRS__ = (
		("len", "i"),
		("offset", "i"),
	)
	def __init__(self, f, s = None):
		Element.__init__(self, f, s)
		pos = f.tell()
		f.seek(self.offset, 0)
		value = f.read(self.len)
		assert value[-1] == '\0'
		self.value = value[:-1]
		f.seek(pos, 0)

class GlobalSequence(Element):
	__ATTRS__ = ( ("value", "i"), )

class Timestamp(Element):
	__ATTRS__ = ( ("value", "i"), )

def MakeAnimBlock(cls):
	if type(cls) == str:
		cls = MakeValue(cls)
	attrs = (
		("interp", "H"),
		("sequenceId", "h"),
		("timestamps", MakeList(Timestamp)),
		("values", MakeList(cls)),
	)
	class ABlock(Element):
		__ATTRS__ = attrs
	ABlock.__name__ = cls.__name__ + "AnimBlock"
	return ABlock

def MakeLookup(cls):
	class Lookup(Element):
		__ATTRS__ = ( ("value", "h"), )
		REF = cls
	Lookup.__name__ = cls.__name__ + "Lookup"
	return Lookup

class Vec3f(Element):
	__ATTRS__ = (
		("x", "f"),
		("y", "f"),
		("z", "f"),
	)

class Mat3x3f(Element):
	__ATTRS__ = ( ( "m", "9f" ), ) #@FIXME

class Vec2f(Element):
	__ATTRS__ = (
		("x", "f"),
		("y", "f"),
	)

class Quat4h(Element):
	__ATTRS__ = (
		("x", "h"),
		("y", "h"),
		("z", "h"),
		("t", "h"),
	)

class Alpha(Element):
	__ATTRS__ = (
		("value", "h"),
	)

class Animation(Element):
	__ATTRS__ = (
		("id", "h",),
		("sub_id", "h",),
		("length", "i",),
		("movingSpeed", "f",),
		("flags", "i",),
		("probability", "h",),
		("unused1", "h",),
		("unknown1", "i",),
		("unknown2", "i",),
		("playbackSpeed", "i",),
		("minExtent", Vec3f),
		("maxExtent", Vec3f),
		("boundRadius", "f",),
		("nextAnimation", "h",),
		("index", "h",),
	)
	__ATTR_REPR__ = ("id","sub_id")

class Bone(Element):
	__ATTRS__ = (
		("key", "i"),
		("flags", "I"),
		("parent", "h"),
		("unknown1", "h"),
		("unknown2", "h"),
		("unknown3", "h"),
		("translation", MakeAnimBlock(Vec3f)),
		("rotation", MakeAnimBlock(Quat4h)),
		("scaling", MakeAnimBlock(Vec3f)),
		("pivot", Vec3f),
	)
	__ATTR_REPR__ = ("key","parent")

class UvAnimation(Element):
	__ATTRS__ = (
		("translation", MakeAnimBlock(Vec3f)),
		("rotation", MakeAnimBlock(Quat4h)),
		("scaling", MakeAnimBlock(Vec3f)),
	)

class Vertex(Element):
	__ATTRS__ = (
		("position", Vec3f),
		("weight1", "B"),
		("weight2", "B"),
		("weight3", "B"),
		("weight4", "B"),
		("bone1", "B"),
		("bone2", "B"),
		("bone3", "B"),
		("bone4", "B"),
		("normal", Vec3f),
		("tex1", Vec2f),
		("tex2", Vec2f),
	)

class Color(Element):
	__ATTRS__ = (
		("color", MakeAnimBlock(Vec3f)),
		("alpha", MakeAnimBlock(Alpha)),
	)

class Texture(Element):
	__ATTRS__ = (
		("type", "I"),
		("flags", "I"),
		("path", StringElement),
	)

class TexReplace(Element):
	__ATTRS__ = ( ("value", "h"), )

class Transparency(Element):
	__ATTRS__ = (
		("alpha", MakeAnimBlock(Alpha)),
	)

class RenderFlag(Element):
	__ATTRS__ = (
		("flag", "h"),
		("mode", "h"),
	)

class TexUnit(Element):
	__ATTRS__ = ( ("value", "h"), )

class KeyBoneLookup(Element):
	__ATTRS__ = ( ("value", "h"), )

class Box(Element):
	__ATTRS__ = (
		("start", Vec3f),
		("end", Vec3f),
		("radius", "f"),
	)

class Attachment(Element):
	__ATTRS__ = (
		("id", "I"),
		("bone", "I"),
		("position", Vec3f),
		("delta", MakeAnimBlock("i")),
	)

class Event(Element):
	__ATTRS__ = (
		("name", "4s"),
		("data", "I"),
		("bone", "I"),
		("position", Vec3f),
		("interp", "H"),
		("sequenceId", "h"),
		("timestamps", MakeList(Timestamp)),
	)

class Light(Element):
	__ATTRS__ = (
		("type", "H"),
		("bone", "h"),
		("position", Vec3f),
		("AmbientColor", MakeAnimBlock(Vec3f)),
		("AmbientIntensity", MakeAnimBlock("f")),
		("DiffuseColor", MakeAnimBlock(Vec3f)),
		("DiffuseIntensity", MakeAnimBlock("f")),
		("AttenuationStart", MakeAnimBlock("f")),
		("AttenuationEnd", MakeAnimBlock("f")),
		("Unknown", MakeAnimBlock("i")),
	)

class Camera(Element):
	__ATTRS__ = (
		("type", "I"),
		("fov", "f"),
		("farClipping", "f"),
		("nearClipping", "f"),
		("trans_pos", MakeAnimBlock(Mat3x3f)),
		("position", Vec3f),
		("trans_tar", MakeAnimBlock(Mat3x3f)),
		("target", Vec3f),
		("scaling", MakeAnimBlock(Vec3f)),
	)

class RibbonEmitter(Element):
	__ATTRS__ = (
		("unknown", "I"),
		("bone", "I"),
		("position", Vec3f),
		("textures", MakeList(Texture)),
		("blendings", MakeList("i")),
		("color", MakeAnimBlock(Vec3f)),
		("opacity", MakeAnimBlock("H")),
		("above", MakeAnimBlock("f")),
		("below", MakeAnimBlock("f")),
		("resolution", "f"),
		("length", "f"),
		("angle", "f"),
		("flag", RenderFlag),
		("unka1", MakeAnimBlock("H")),
		("unka2", MakeAnimBlock("c")),
		("padding", "i"),
	)

#~ class ParticleEmitter(Element): #@FIXME
	#~ __ATTRS__ = (
	#~ )

class BoneReference(Element):
	__ATTRS__ = (
		("bone1", "B"),
		("bone2", "B"),
		("bone3", "B"),
		("bone4", "B"),
	)

class Mesh(Element):
	__ATTRS__ = (
		("id", "I"),
		("start_vertex", "H"),
		("num_vertex", "H"),
		("start_triangle", "H"),
		("num_triangle", "H"),
		("start_bone", "H"),
		("num_bone", "H"),
		("unknown1", "H"),
		("root_bone", "H"),
		("box", Box),
	)

class TextureUsage(Element):
	__ATTRS__ = (
		("flags", "H"),
		("shading", "H"),
		("mesh", "H"),
		("mesh2", "H"),
		("color", "h"),
		("render-flags", "H"),
		("tex-unit1", "H"),
		("mode", "H"),
		("texture", "H"),
		("tex-unit2", "H"),
		("transparency", "H"),
		("texture-anim", "H"),
	)

class Skin:
	CONSTRUCTORS = {
		"indices": MakeList("H"),
		"triangles": VertexList,
		"properties": MakeList(BoneReference),
		"meshes": MakeList(Mesh),
		"tex-units": MakeList(TextureUsage),
		"bones": MakeValue("I", "Bone"),
	}
	ELEMENTS = [
		"indices",
		"triangles",
		"properties",
		"meshes",
		"tex-units",
		"bones",
	]
	def __init__(self, skin):
		f = open(skin, "rb")
		def unpack(fmt, size, f):
			return struct.unpack(fmt, f.read(size))

		self.magic = unpack("<4s", 4, f)[0]
		print "Magic", self.magic
		assert self.magic == "SKIN"

		for name in self.ELEMENTS:
			cls = self.CONSTRUCTORS.get(name)
			if cls:
				value = cls(f)
				print name, value
				setattr(self, name.replace("-", "_"), value)
		f.close()

class Model:
	CONSTRUCTORS = {
		"magic": MakeValue("4s", "Magic"),
		"flags": MakeValue("i", "Flag"),
		"nviews": MakeValue("i", "Integer"),
		"name": StringElement,
		"global-sequences": MakeList(GlobalSequence),
		"animations": MakeList(Animation),
		"animation-lookup": MakeList(MakeLookup(Animation)),
		"bones": MakeList(Bone),
		"key-bone-lookup": MakeList(KeyBoneLookup),
		"vertices": MakeList(Vertex),
		"colors": MakeList(Color),
		"textures": MakeList(Texture),
		"transparency": MakeList(Transparency),
		"uv-animation": MakeList(UvAnimation),
		"tex-replace": MakeList(TexReplace),
		"render-flags": MakeList(RenderFlag),
		"bone-lookup": MakeList(MakeLookup(Bone)),
		"tex-lookup": MakeList(MakeLookup(Texture)),
		"tex-units": MakeList(TexUnit),
		"trans-lookup": MakeList(MakeLookup(Transparency)),
		"uv-anim-lookup": MakeList(MakeLookup(UvAnimation)),
		"vbox": Box,
		"bbox": Box,
		"bounding-triangles": VertexList,
		"bounding-vertices": MakeList(Vec3f),
		"bounding-normals": MakeList(Vec3f),
		"attachments": MakeList(Attachment),
		"attach-lookup": MakeList(MakeLookup(Attachment)),
		"events": MakeList(Event),
		"lights": MakeList(Light),
		"cameras": MakeList(Camera),
		"camera-lookup": MakeList(MakeLookup(Camera)),
		"ribbon-emitters": MakeList(RibbonEmitter),
		#~ "particle-emitters": MakeList(TexReplace),
	}
	ELEMENTS = [
		"name",
		"flags",
		"global-sequences",
		"animations",
		"animation-lookup",
		"bones",
		"key-bone-lookup",
		"vertices",
		"nviews",
		"colors",
		"textures",
		"transparency",
		"uv-animation",
		"tex-replace",
		"render-flags",
		"bone-lookup",
		"tex-lookup",
		"tex-units",
		"trans-lookup",
		"uv-anim-lookup",
		"vbox",
		"bbox",
		"bounding-triangles",
		"bounding-vertices",
		"bounding-normals",
		"attachments",
		"attach-lookup",
		"events",
		"lights",
		"cameras",
		"camera-lookup",
		"ribbon-emitters",
		"particle-emitters",
	]

	def __init__(self, model, skin = None):
		f = open(model, "rb")
		def unpack(fmt, size, f):
			return struct.unpack(fmt, f.read(size))

		self.magic = unpack("<4s", 4, f)[0]
		print "Magic", self.magic
		assert self.magic == "MD20"
		self.version = unpack("<4b", 4, f)
		print "Version", self.version

		for name in self.ELEMENTS:
			cls = self.CONSTRUCTORS.get(name)
			if cls:
				value = cls(f)
				print name, value
				setattr(self, name.replace("-", "_"), value)
		f.close()
		if skin:
			self.skin = Skin(skin)

m = Model("humanmale.m2", "humanmale00.skin")
